import asyncio
import base64
import datetime
import json
import logging
import random
import sys
import time
import urllib
from collections import defaultdict
from datetime import timedelta
from json import JSONDecodeError

import aiohttp
import aioredis
import pymongo
import tornado
from faker import Faker
from tornado import gen

from baseHandler import Application, PayHandler, init_logger, http_result
from util.cmcc_10085 import get_weixin_h5, cmcc_query
from util.cmcc_10086 import cmcc10086_qrcode, cmcc_10086_query, order_cancel

lock = asyncio.Lock()
RECHANGE_QUEUE = defaultdict(asyncio.Queue)

INTEVAL = 0.2
QUERY_TIME = 60 * 6


def get_ts():
    return str(time.time()).replace('.', '')[:13]



# async def aget_result(orderid, url, redis):
#     faker = Faker(locale='zh_CN')
#     ip = faker.ipv4()
#     headers = {
#         'User-Agent': faker.chrome(),
#         'CLIENT-IP': ip,
#         'X-FORWARDED-FOR': ip,
#         "Referer": url
#     }
#     if __name__ == '__main__':
#         logger = logging.getLogger('cmcc')
#     else:
#         logger = logging.getLogger('recharge_noti')
#     proxys = await redis.smembers('proxy', encoding='utf-8')
#     proxy = random.choice(list(proxys))
#     if proxy:
#         conn = proxy
#     else:
#         conn = None
#     async with aiohttp.ClientSession(headers=headers) as session:
#         url = 'https://pay.shop.10086.cn/paygw/fnpay/%s-20.html' % orderid
#         async with session.get(url, proxy=conn) as resp:
#             if resp.status != 200:
#                 logger.error(resp.status)
#                 return '9', '请求出错'
#             query_url = str(resp.url)
#         params = urllib.parse.urlparse(query_url).query
#         params = '%s&_=%s' % (params, get_ts())
#         url = 'https://shop.10086.cn/i/v1/pay/paycallback?' + params
#         headers['Host'] = 'shop.10086.cn'
#         headers['Referer'] = query_url
#         headers['X-Requested-With'] = 'XMLHttpRequest'
#         async with session.get(url, proxy=conn) as resp:
#             res_rsp = await resp.json()
#             logger.info(res_rsp)
#             if res_rsp['data']['status'] == '0':
#                 orderstate = '0'
#             else:
#                 orderstate = '8'
#             return orderstate, res_rsp['data']['payDate']


class CmccHandler(PayHandler):
    @gen.coroutine
    def post(self):
        logger.info(self.request.path)
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["ordernumber", "amount", "tel_number"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})
        # prov = data.get("prov", "")
        konghao = yield self.application.redis.sismember("konghao", data['tel_number'])
        if konghao:
            logger.info("%s空号不可用", data['tel_number'])
            return self.write({"ret": -1, "message": "空号不可用"})

        logger.info("充值%s|%s|%s", data['ordernumber'], data['tel_number'], data['amount'])
        res = self.check_ordernumber(data)
        if res:
            return self.write(res)

        if data["amount"] not in ('10', '20', '30', '50', '100', '200', '300', '400', '500'):
            return self.write({"message": "不支持的金额", "ret": -1})

        start_time = time.time()
        obj = {
            "tel_number": data["tel_number"],
            "ordernumber": data["ordernumber"],
            "time": start_time,
            "amount": data["amount"]
        }

        data['time'] = start_time
        data["orderdate"] = datetime.datetime.now().strftime('%Y%m%d')
        data["merchantid"] = self.merchantid
        data['tel_type'] = 'cmcc'
        data['orderstate'] = '2'
        try:
            yield db.rechange.insert_one(data)
            self.application.redis.lpush('recharge', json.dumps({"ordernumber": data['ordernumber']}))
        except pymongo.errors.DuplicateKeyError:
            return self.write({"ret": -1, "message": "重复的订单号"})

        if data["amount"] in ('10', '20', '30', '50', '100', '200', '300', '400', '500'):
            queue_name = "%s_%s" % (self.merchantid, data["amount"])
            # yield from RECHANGE_QUEUE[queue_name].put(obj)
            yield self.application.redis.lpush(queue_name, json.dumps(obj))
            queue_size = yield self.application.redis.llen(queue_name)
            logger.info(queue_size)
        else:
            return self.write({"message": "不支持的金额", "ret": -1})

        res = {"orderstate": '2', "message": "订单提交成功", }
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)
        self.finish()


class WxHandler(PayHandler):
    @gen.coroutine
    def post(self):
        # is_close = yield self.application.redis.get("is_cmcc86_close", encoding='utf-8')
        # if is_close == '1':
        #     logger.info("%s|通道关闭")
        #     return self.write({"ret": -1, "message": "通道关闭"})
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["payorderno", "amount"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})
        obj = yield db.rechange.find_one({"payorderno": data["payorderno"]})
        if obj:
            logger.info(obj)
            return self.write({"message": "订单号重复", "ret": -1})
        logger.info("支付订单%s|%s|%s", self.merchantid, data["payorderno"], data['amount'])
        supplier = self.merchant_info.get("supplier")
        if not supplier:
            return self.write({"message": "无有效供应商", "ret": -1})


        while True:
            try:
                pre_queue_name = "PRE_CMCC_%s_%s" % (supplier, data["amount"])
                result = yield self.application.redis.rpop(pre_queue_name, encoding='utf-8')
                if result:
                    result = json.loads(result)
                    ordernumber = result.get("ordernumber")
                    obj = yield db.rechange.find_one({"ordernumber": ordernumber})
                    if obj['orderstate'] == '1':
                        continue
                    if time.time() - result["time"] > 175:
                        logger.info("%s超过支付时间" % ordernumber)
                        continue
                    logger.info("预|%s|%s|%s", pre_queue_name, ordernumber, data["payorderno"])
                    user_data = result.get('userdata')
                    username, passwd, use_time = user_data.split("|")
                else:
                    # 提取账号
                    user_data = yield self.application.redis.rpop("cmcc_user", encoding='utf-8')
                    if user_data:
                        username, passwd, use_time = user_data.split("|")
                    else:
                        return self.write({'ret': -1, 'message': "没有账号"})
                    if float(use_time) > time.time():
                        yield self.application.redis.lpush("cmcc_user", user_data)
                        return self.write({'ret': -1, 'message': "账号下单超限"})
                    #####
                    queue_name = "%s_%s" % (supplier, data["amount"])
                    logger.info(queue_name)
                    obj = yield self.application.redis.rpop(queue_name, encoding='utf-8')
                    if not obj:
                        yield self.application.redis.lpush("cmcc_user", user_data)
                        return self.write({'ret': -1, 'message': "无有效订单"})
                    obj = json.loads(obj)
                    if time.time() - obj["time"] > 58:
                        if time.time() - obj['time'] < 60:
                            yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                         {"$set": {
                                                             "orderstate": "1"
                                                         }})
                        continue
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set": {
                                                     "orderstate": "8"
                                                 }})

                    try:
                        pay_channel = data.get("pay_channel")
                        future = cmcc10086_qrcode(obj["tel_number"], obj['amount'], self.application.redis, pay_channel, username, passwd)
                        result = yield gen.with_timeout(timedelta(seconds=30), future)
                    except Exception as e:
                        logger.info("%s取单失败", obj["tel_number"])
                        logger.error(e, exc_info=True)
                        result = {"code": 2, 'message': "取单失败"}
                        if result['code'] == "511004":
                            use_time = time.time() + 30 * 60
                            user_data = "%s|%s|%s" % (username, passwd, use_time)
                            yield self.application.redis.lpush('cmcc_user', user_data)
                        elif result['code'] == "511005":
                            logger.info("下单超限")
                        else:
                            user_data = "%s|%s|%s" % (username, passwd, use_time)
                            yield self.application.redis.lpush('cmcc_user', user_data)

                wx_url = result.get('qrcode')
                order_url = result.get('url')
                order_id = result.get('orderid')
                message = result.get("message")
                prov = result.get('prov')
                logger.info('%s|%s|%s', obj['ordernumber'], username, message)
                if wx_url:
                    orderstate = '8'
                else:
                    orderstate = '1'
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set":  {"orderstate": orderstate, "message": message}}
                                                 )

                url = 'http://qr.nkmcd.com/charge/v3.php?ordernumber=%s' % obj["ordernumber"]
                res = {"orderstate": orderstate, "message": message, "url": order_url,
                        'wx_url': url,
                       "orderid": order_id}
                if orderstate == '8':
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set": {'order_url': order_url,
                                                           'wx_url': wx_url,
                                                           "payorderno": data["payorderno"],
                                                           "merchantuid": self.merchantid,
                                                           "orderstate": orderstate,
                                                           "orderid": order_id,
                                                           "pay_backurl": data.get("backurl"),
                                                           "username": username,
                                                           'prov': prov
                                                           }
                                                  })
                return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
                return_obj.update({"ret": 0, "message": "success"})
                self.write(return_obj)
                self.finish()
                return
            except asyncio.QueueEmpty:
                return self.write({'ret': -1, 'message': "无有效订单"})



class WxH5Handler(PayHandler):
    @gen.coroutine
    def post(self):
        is_close = yield self.application.redis.get("is_cmcc85_close", encoding='utf-8')
        if is_close == '1':
            logger.info("%s|通道关闭")
            return self.write({"ret": -1, "message": "通道关闭"})
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["payorderno", "amount"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})

        obj = yield db.rechange.find_one({"payorderno": data["payorderno"]})

        if obj:
            logger.info(obj)
            return self.write({"message": "订单号重复", "ret": -1})
        logger.info("支付订单%s|%s|%s", self.merchantid, data["payorderno"], data['amount'])
        supplier = self.merchant_info.get("supplier")
        if not supplier:
            return self.write({"message": "无有效供应商", "ret": -1})

        while True:
            try:
                if data["amount"] in ('10', '20', '30', '50', '100', '200', '300', '400', '500'):
                    queue_name = "%s_%s" % (supplier, data["amount"])
                    logger.info(queue_name)
                    # obj = RECHANGE_QUEUE[queue_name].get_nowait()
                    obj = yield self.application.redis.rpop(queue_name, encoding='utf-8')
                else:
                    return self.write({"message": "不支持的金额", "ret": -1})
                # logger.info(obj)
                if not obj:
                    return self.write({'ret': -1, 'message': "无有效订单"})
                obj = json.loads(obj)
                if time.time() - obj["time"] > 58:
                    if time.time() - obj['time'] < 60:
                        yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                     {"$set": {
                                                         "orderstate": "1"
                                                     }})
                    continue
                yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                             {"$set": {
                                                 "orderstate": "8"
                                             }})
                result = None
                for _ in range(5):
                    try:
                        if data.get("pay_channel") == 'ali':
                            future = get_weixin_h5(obj["tel_number"], obj['amount'], self.application.redis, 'ali')
                        elif data.get("pay_channel") == 'wxqrcode':
                            future = get_weixin_h5(obj["tel_number"], obj['amount'], self.application.redis, 'wxqrcode')
                        else:
                            future = get_weixin_h5(obj["tel_number"], obj['amount'], self.application.redis)
                        result = yield gen.with_timeout(timedelta(seconds=30), future)
                        break
                    except aiohttp.ClientConnectorError as e:
                        logger.info("%s|代理失效", obj["tel_number"])
                        logger.error(e, exc_info=True)
                    except aiohttp.client_exceptions.ClientOSError as e:
                        logger.info("%s|代理失效2", obj["tel_number"])
                        logger.error(e, exc_info=True)
                    except aiohttp.client_exceptions.ServerDisconnectedError as e:
                        logger.info("%s|代理失效3", obj["tel_number"])
                        logger.error(e, exc_info=True)
                    except aiohttp.client_exceptions.ClientResponseError as e:
                        logger.info("%s|代理失效4", obj["tel_number"])
                        logger.error(e, exc_info=True)
                    except JSONDecodeError as e:
                        logger.error(e, exc_info=True)
                    except Exception as e:
                        logger.info("%s取单失败", obj["tel_number"])
                        logger.error(e, exc_info=True)
                        break
                if result:
                    wx_url, order_id, prov = result
                else:
                    wx_url = None
                    order_id = None
                    prov = None
                if wx_url:
                    orderstate = '8'

                    if data.get("pay_channel") == 'wxqrcode':
                        url = 'http://qr.nkmcd.com/charge/qrcode.php?ordernumber=%s' % obj["ordernumber"]
                    else:
                        url = "http://qr.nkmcd.com/charge/v2.php?ordernumber=%s" % obj["ordernumber"]
                else:
                    orderstate = '1'
                    url = ""
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set":  {"orderstate": orderstate}}
                                                 )

                res = {"orderstate": orderstate, "message": "success",  'wx_url': url,
                       "orderid": order_id}
                if orderstate == '8':
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set": {
                                                        "payorderno": data["payorderno"],
                                                           "merchantuid": self.merchantid,
                                                           "orderstate": orderstate,
                                                           "orderid": order_id,
                                                           "pay_backurl": data.get("backurl"),
                                                            "wx_url": wx_url,
                                                            "pay_type": "wx_h5",
                                                            "prov": prov,
                                                           }
                                                  })
                # else:
                #     continue
                return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
                return_obj.update({"ret": 0, "message": "success"})
                self.write(return_obj)
                self.finish()
                return
            except asyncio.QueueEmpty:
                return self.write({'ret': -1, 'message': "无有效订单"})


async def query(query_obj, db, redis):
    now = time.time()
    obj = await db.rechange.find_one(query_obj)
    extra = {}
    if not obj:
        orderstate = "1"
        message = "订单号不存在"
        return orderstate, message, extra
    orderstate = obj.get("orderstate", "2")

    if orderstate == "0" or orderstate == "1":
        message = obj.get("message", "")
        extra = {
            "tel_number": obj["tel_number"],
            "orderid": obj.get("orderid", ""),
        }
    else:
        async with lock:
            obj = await db.rechange.find_one(query_obj)
            query_time = obj.get("query_time") or obj["time"]
            if time.time() - query_time < 15:
                message = ""
                return orderstate, message, extra

            query_time = time.time()
            await db.rechange.update_one(query_obj, {"$set": {"query_time": query_time}})

        if obj.get('orderid'):
            if obj.get('orderid').startswith("3"):
                orderstate, message = await cmcc_query(obj['orderid'], obj.get("tel_number"), redis)
            else:
                username = obj.get("username")
                orderdate = obj.get("orderdate")
                user_data = await db.cmcc_username.find_one({'username': username})
                passwd = user_data['passwd']
                orderstate, message = await cmcc_10086_query(obj['orderid'], redis, username, passwd, orderdate)
            if orderstate == '9':
                await redis.sadd("cmcc_9", obj['ordernumber'])
            if orderstate == '8':
                if query_time - obj['time'] > QUERY_TIME:
                    orderstate = '1'
                    if not obj.get('orderid').startswith("3"):
                        await order_cancel(obj['orderid'], username, passwd, redis)
                else:
                    orderstate = obj.get("orderstate")
            extra = {
                "tel_number": obj["tel_number"],
                "orderid": obj.get("orderid", ""),
                # "url": obj.get("url", "")
            }
        else:
            if time.time() - obj['time'] < 60 and obj.get('orderstate', '2') == '2':
                orderstate = '2'
                message = obj.get("message", "取单中")
            elif time.time() - obj['time'] < 92 and obj.get('orderstate', '2') == '8':
                orderstate = '8'
                message = '获取二维码中'
            else:
                orderstate = '1'
                message = '未支付2'
            extra = {
                "tel_number": obj["tel_number"],
                "orderid": obj.get("orderid", ""),
                # "url": obj.get("url", "")
            }
        await db.rechange.update_one(query_obj, {
            "$set": {"orderstate": orderstate, "query_time": query_time, "message": message}})
    return orderstate, message, extra


class CmccQuery(PayHandler):
    @gen.coroutine
    def post(self):
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)

        if data.get("ordernumber"):
            query_obj = {"ordernumber": data["ordernumber"]}
        elif data.get("payorderno"):
            query_obj = {"payorderno": data["payorderno"]}

        else:
            return self.write({"message": "无效参数", "ret": -1})
        orderstate, message, extra = yield query(query_obj, db, self.application.redis)
        logger.info("查询%s|%s|%s|%s",  data.get("ordernumber"), data.get("payorderno"), orderstate, message)
        res = {"orderstate": orderstate, "message": message, **extra}
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)


class QueueHandler(PayHandler):
    @gen.coroutine
    def post(self):
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        res = {k: v.qsize() for k, v in RECHANGE_QUEUE.items()}
        logger.info(res)
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)


class SetTimeHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self, query_time):
        if int(query_time) < (6 * 60):
            return self.write("failed")
        QUERY_TIME = query_time
        return self.write('sucess')


if __name__ == "__main__":
    handlers = [
        (r"/pay/recharge", CmccHandler),
        (r"/pay/wxqrcode", WxHandler),
        (r"/pay/wxh5", WxH5Handler),
        (r"/pay/query", CmccQuery),
        (r"/pay/queue", QueueHandler),
        (r"/set_querytime/(\d+)", SetTimeHandler)
    ]
    QUERY_TIME = 60 * 6
    application = Application(handlers, debug=False)
    port = sys.argv[1]
    application.listen(port, address="127.0.0.1")
    logger = init_logger("cmcc", "/app/log/cs_cmcc/cmcc%s.log" % port)
    logger.info("Run unionpay server ... http://127.0.0.1:%s", port)
    loop = asyncio.get_event_loop()
    application.redis = loop.run_until_complete(
        aioredis.create_redis(address=('localhost', 6379), db=0, loop=loop)
    )
    application.init_with_loop(loop)
    loop.run_forever()
